#light

(* Bind the document graph and the cgxml system.
  Upon load, perform a datatype search to locate all
  of the various different urls, ids and things and create
  new entries in the document graph.  This operation may
  or may not be undoable, depending on the context; i.e. loading
  a new project is not an undoable operation, but adding a new file
  to an existing project is.  Likewise, loading a new project is
  an extremely time-sensitive operation while adding a new file
  to a project is not.  This is where things begin to get complex,
  however.
  
  Given this binding object, I would like adding and changing the
  underlying xml information to be as simple as possible.  Common
  operations include adding/removing groups of sub nodes and changing
  attribute values.  I would like some minimal syntax that given
  the parent node I pass in a structure that completely identifies
  what new objects to add and the system automatically updates the
  document graph, creates unique ids, and adds the information all
  in one undo-able step.  Also there should be a method so that
  you can pass in url references as nodes in the document graph and
  have the system automatically fill them out.
  
  
  Finally, there will need to be a pre-save hook that updates the
  document with correct urls.
  
  Once this is in place, and basic functionality is
  tested then it will quickly be time to ensure that unloading and
  reloading different projects releases memory.  I believe I will
  make the external reference to the system very opaque; i.e. handles
  of some sort.  In this way there can be no references lying in odd
  places that would keep old projects (or the system) in memory. *)

/// Document graph xml layer
module CGraph.DGXml

open CGraph.DocumentGraph
open CGraph.DirectedGraph
open System.Xml.Schema
open System.Xml
open CGraph.Generic
open CGraph.Generic.Algorithms;
open Microsoft.FSharp.Collections


type ElemOrAtt = CGraph.CGXml.ElementOrAttribute
   
/// Objects directly represented within an xml document are:
/// Objects -> XmlElement
/// Ids -> xs:ID attribute or element objects
/// ObjIdRef -> xs:anyURI, dae:URIFragment, IDREF, IDREFS
/// ObjPathRef -> xs:anyURI (may refer to a document, not just an id)
type DGXmlObject =
    | Obj of ObjectVertex
    // Since elements can derive from simple types, 
    // all of the datatypes below may map to both an object vertex
    // *and* their respective datatypes.  There is an object vertex
    // created for every xml element we care about.  A simplification
    // would be to only create them for non-simple XmlSchemaTypes,
    // i.e. directly derived with no attributes, but at this point
    // I don't care.
    | Id of IdVertex
    | ObjIdRef of (ObjIdRefEdge List)*ObjectVertex option //anyURI, IDREF, or URIFragments always map to edges
    | ObjPathRef of ObjPathRefEdge*ObjectVertex option //anyURI may map to a path reference
    
/// This handle is generated, since the xml system doesn't generate
/// unique handles for attributes.
type VertexEntry = { Vertex:DGXmlObject; VertHandle:int; XmlVal:ElemOrAtt }
    
/// Map vertexes to xml documents, elements or attributes
type VertexXmlCollection = MultiCollection<VertexEntry>

type VertexXmlVertIndex() =
    inherit MultiCollectionIndex<VertexEntry,int>(fun(entry)->Some entry.VertHandle)
    
type VertexXmlXmlIndex() =
    inherit MultiCollectionIndex<VertexEntry,ElemOrAtt>(fun(entry)->Some entry.XmlVal)

type VertexXmlObjVertIndex() =
    inherit MultiCollectionIndex<VertexEntry,ObjectVertex>(
        fun(entry) ->
            match entry.Vertex with
                | Obj(objVert)->Some objVert
                | _ -> None )

/// Xml document representation    
type XmlDocument = { 
    XmlHandle:CGraph.CGXml.DocumentHandle;
    ///All of these vertexes must be in the document.  Move operations should
    ///update all of this information.
    Vertexes:VertexXmlCollection;
    XmlIndexes:VertexXmlXmlIndex; //Indexes by xml handles
    ObjectVertexes:VertexXmlObjVertIndex;
    }
    
let createXmlDocument hdl (vhdls:VertexXmlVertIndex)=
    let vcol = MultiCollection<VertexEntry>()
    let xmlHdls = VertexXmlXmlIndex()
    let objVertexes = VertexXmlObjVertIndex();
    vcol.AddSubCollection vhdls
    vcol.AddSubCollection xmlHdls
    vcol.AddSubCollection objVertexes
    { XmlHandle=hdl;Vertexes=vcol;XmlIndexes=xmlHdls;ObjectVertexes=objVertexes }
    
/// Right now we just have xml documents.  But this could easily be plugged, and
/// there will need to be support for at least images also, and probably
/// there should be an fbx entry
type DocumentTypes =
    | XmlDocument of XmlDocument
    | Unknown of obj

/// Uses the cgxml handle as the document handle
type DocEntry = { 
    DocVert:DocumentVertex;
    DocHandle:int;
    Document:DocumentTypes;
    }
    
let createDocEntry dv dh dt = { DocVert=dv;DocHandle=dh;Document=dt }
  
type DocEntryCollection = MultiCollection<DocEntry>

type DocEntryHandleIndex() = 
    inherit MultiCollectionIndex<DocEntry,int>(fun entry -> Some entry.DocHandle)
    
type XmlDocEntryHandleIndex() =
    inherit MultiCollectionIndex<DocEntry,CGraph.CGXml.DocumentHandle>(
        fun entry ->
            match entry.Document with
                | XmlDocument(doc) -> Some (doc.XmlHandle)
                | _ -> None )
                
type DocumentHdl = { Handle:int }

type DocumentItemHdl = { Handle:int }

type DocumentGroup =
    val private m_xmlGroup:CGraph.CGXml.DocumentGroup
    val private m_docGraph:DocumentGraph
    val private m_documents:MultiCollection<DocEntry>
    val private m_handleToDoc:DocEntryHandleIndex
    val private m_xmlDocToDoc:XmlDocEntryHandleIndex
    val private m_handles:Incrementor<int>
    val private m_docSearchCtx:CGraph.CGXml.DatatypeSearchContext
    val private m_vertexHandles:VertexXmlVertIndex
    new(set:XmlSchemaSet) as s = 
        {
            m_xmlGroup = CGraph.CGXml.DocumentGroup(set)
            m_docGraph = DocumentGraph()
            m_documents = MultiCollection<DocEntry>()
            m_handles = Incrementor<int>( (fun item -> item + 1), 1)
            m_handleToDoc = DocEntryHandleIndex()
            m_xmlDocToDoc = XmlDocEntryHandleIndex()
            m_docSearchCtx = CGraph.CGXml.DatatypeSearchContext( [|"http://www.w3.org/2001/XMLSchema","ID"; 
                    "http://www.w3.org/2001/XMLSchema","IDREF";
                    "http://www.w3.org/2001/XMLSchema","IDREFS";
                    "http://www.w3.org/2001/XMLSchema","anyURI";
                    "http://www.collada.org/2005/11/COLLADASchema","URIFragmentType"; |] )
            m_vertexHandles = VertexXmlVertIndex()
        } 
        then
            s.m_documents.AddSubCollection s.m_handleToDoc
            s.m_documents.AddSubCollection s.m_xmlDocToDoc
            
    member s.GetOrLoad path =
        let loaded,doc = s.m_xmlGroup.GetOrLoad path
        if (not loaded)
            then 
                //Run through document creating entries for the ids and any uris.
                let pathVert = s.m_docGraph.GetOrCreatePathVertex( path )
                let docVert = s.m_docGraph.CreateDocumentVertex()
                s.m_docGraph.MapPath( pathVert, docVert )               
                let docHdl = s.m_handles.Next
                let xmlDoc = createXmlDocument doc s.m_vertexHandles
                let docEntry = createDocEntry docVert s.m_handles.Next (XmlDocument xmlDoc)
                s.m_documents.Add docEntry
                
                let callback = s.DocSearchCallback docVert doc xmlDoc
                    
                let callback = CGraph.CGXml.FindTypeCallback(callback)                
                s.m_xmlGroup.FindDatatypes( doc, (s.m_xmlGroup.DocumentElement doc)
                    , s.m_docSearchCtx, callback )     
        { new DocumentHdl with Handle=s.m_xmlDocToDoc.Items.[doc].DocHandle }
               
    /// Callback when doing a FindType search.
    /// doc is the xml document's handle, xmlDoc is the overlying document entry.
    member private s.DocSearchCallback (docVert:DocumentVertex) (doc:CGraph.CGXml.DocumentHandle) (xmlDoc:XmlDocument) (eora:CGraph.CGXml.ElementOrAttribute) (ns:string) (name:string) (info:IXmlSchemaInfo) (value:string) =
        let entry = 
            match name with
                | _ when name = "ID" ->
                    let idVert = s.m_docGraph.GetOrCreateIdVertex (value,docVert)
                    let hdl = eora.Element
                    match eora.Attribute with
                        | Some (ns,name) ->
                            let objVert = s.GetOrAddObjectVertex eora.Element
                            s.m_docGraph.MapId( idVert,objVert )
                            { Vertex=Id(idVert); VertHandle=s.m_handles.Next; XmlVal=eora }
                        | None -> failwith "Not expecting elements of the ID datatype"
                | _ ->
                    let objVert,objVertOption,elemOrAtt =
                        match eora.Attribute with
                            | Some (ns,name) -> (s.GetOrAddObjectVertex eora.Element),None,eora
                            | None -> //This element directly derived from the object vertex
                                let retval = s.CreateObjectVertex docVert
                                retval,Some retval,eora
                    let values = s.m_xmlGroup.GetValues eora
                    let xmlObj = 
                        match values with
                            | CGraph.CGXml.SLList(listVals) ->
                                //These need to all be id, id,doc refs
                                //You wouldn't get a list of anyURI values, which are
                                //the only values that can legally reference outside the local
                                //document
                                let mutable refList = []
                                for idref in listVals do
                                    let idRef = ParseXSDId idref
                                    let targetId = 
                                        match idRef with
                                            | XSDDocId(doc,id) -> 
                                                let vertDoc = s.m_docGraph.GetOrCreateDocumentByPath doc
                                                s.m_docGraph.GetOrCreateIdVertex (id,vertDoc)
                                            | XSDId(id) -> s.m_docGraph.GetOrCreateIdVertex (id,docVert)
                                            | _ -> failwith( "Unexpected id type" )
                                    let refEdge = s.m_docGraph.RefId( objVert, targetId )
                                    refList <- refEdge :: refList
                                ObjIdRef(refList,objVertOption)
                            | CGraph.CGXml.SLString(str) -> 
                                let idRef = ParseXSDId str
                                match idRef with
                                    | XSDDocId(doc,id) ->
                                        let vertDoc = s.m_docGraph.GetOrCreateDocumentByPath doc
                                        let idVert = s.m_docGraph.GetOrCreateIdVertex (id,vertDoc)
                                        ObjIdRef([s.m_docGraph.RefId(objVert,idVert)],objVertOption)
                                    | XSDId(id) -> 
                                        let idVert = s.m_docGraph.GetOrCreateIdVertex (id,docVert)
                                        ObjIdRef([s.m_docGraph.RefId(objVert,idVert)],objVertOption)
                                    | XSDDoc(doc) ->
                                        let vertPath = s.m_docGraph.GetOrCreatePathVertex doc
                                        ObjPathRef(s.m_docGraph.RefPath(objVert,vertPath),objVertOption)
                    { Vertex=xmlObj; VertHandle=s.m_handles.Next; XmlVal=elemOrAtt }       
        xmlDoc.Vertexes.Add entry 
                    
    member private s.CreateObjectVertex docVert =
        let objVert = s.m_docGraph.CreateObjectVertex()
        s.m_docGraph.MapDocument(docVert,objVert)
        objVert
        
    member private s.GetOrAddObjectVertex (docHdl:int) (hdl:int) =
        let entry = s.m_xmlDocToDoc.Items.[docHdl]
        match entry.Document with
            | XmlDocument(xmlDoc) -> 
                let elemVal = { CGraph.CGXml.Element=hdl; CGraph.CGXml.Attribute=None }
                let success,existing = xmlDoc.XmlIndexes.Items.TryGetValue elemVal
                if (not success)
                    then 
                        let objVert = s.CreateObjectVertex entry.DocVert
                        let dgObj = DGXmlObject.Obj(objVert)
                        let entry = { Vertex=dgObj; VertHandle=s.m_handles.Next; XmlVal=elemVal }
                        xmlDoc.Vertexes.Add entry
                        objVert
                    else
                        match existing.Vertex with
                            | Obj(objVert) -> objVert
                            | _ -> failwith "Invalid logic"
            | _ -> failwith "Invalid logic somewhere"
    
    ///Returns an xml vertex entry option
    member s.GetById ((docHdl:DocumentHdl),(id:string)) =
        let realDoc = s.m_handleToDoc.Items.[docHdl.Handle]
        let idVert = realDoc.DocVert.GetId id
        match idVert with
            | None -> None
            | Some idVert ->
                //Find the object vertex.
                let objVert = idVert.PrimaryObjectVertex
                match objVert with
                    | None -> None
                    | Some vert ->
                        match realDoc.Document with
                            | XmlDocument(xmlDoc) ->
                                let success,entry = xmlDoc.ObjectVertexes.Items.TryGetValue vert
                                if success then
                                    Some { new DocumentItemHdl with Handle=entry.VertHandle }
                                else None
                            | _ -> None
                            
    //member private s.GetXmlDocument (itemHdl:DocumentItemHdl)
    //    let entry = s.m_vertexHandles.Items.[itemHdl.Handle]
        
                            
    member s.GetAttribute (itemHdl:DocumentItemHdl,ns:string,name:string) =
        let success,entry = s.m_vertexHandles.Items.TryGetValue itemHdl.Handle
        if ( success )
            then
                let xmlDoc = s.GetXmlDocument(itemHdl) 
                Some (s.m_xmlGroup.GetAttribute (xmlDoc, entry.XmlVal.Element, ns, name ))
            else None
                